package busuanzi.module;


import busuanzi.storage.MongoClient;
import busuanzi.util.Asserts;
import busuanzi.util.Containers;
import busuanzi.util.Sugars;
import busuanzi.util.UUIDs;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.ReturnDocument;
import org.bson.Document;
import org.jasypt.util.password.StrongPasswordEncryptor;

import java.util.Date;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static busuanzi.storage.MongoClient.newDocument;
import static busuanzi.storage.MongoClient.s;
import static busuanzi.util.Containers.newHashMap;
import static busuanzi.util.Strings2.hasEmpty;
import static com.google.common.base.Strings.isNullOrEmpty;
import static sardine.Sardine.*;


/**
 * Auth: bruce-sha
 * Date: 2015/4/29
 * <p/>
 * 100-199 用于指定客户端应相应的某些动作。
 * 200-299 用于表示请求成功。
 * 300-399 用于已经移动的文件并且常被包含在定位头信息中指定新的地址信息。
 * 400-499 用于指出客户端的错误。
 * 500-599 用于支持服务器错误。
 * http://www.restapitutorial.com/httpstatuscodes.html
 * <p/>
 * 用户登录设计
 * http://coolshell.cn/articles/5353.html
 * http://blog.farbox.com/post/redesign-login-system
 */
public abstract class Securities {

    static final StrongPasswordEncryptor encryptor = new StrongPasswordEncryptor();
    static final int ERROR_SIGNUP = 538;
    static final int ERROR_SIGNIN = 539;

    public static final long SECURITY_MAX_AGE = 24 * 3600 * 1000; //24小时
    public static final String SECURITY_USER_NAME = "un";//用户名:明文
    public static final String SECURITY_SERIAL_NUMBER = "sn";//登录序列号：强制用户输入口令，如修改密码时
    public static final String SECURITY_TOKEN = "tn";//登录token：一个session内有效，每次更新


    public static void run() {

        before("/signin.html", (request, response) -> {

            String userName = request.cookie(Securities.SECURITY_USER_NAME);
            String serialNumber = request.cookie(Securities.SECURITY_SERIAL_NUMBER);
            String busuanziToken = request.cookie(Securities.SECURITY_TOKEN);

            if (hasEmpty(userName, serialNumber, busuanziToken)) return;

            MongoClient.collection("busuanzi/token").wash(tokens -> {
                final long now = System.currentTimeMillis();
                final Document token = tokens.find(s("{un: '%s', sn: '%s',tn: '%s'}", userName, serialNumber, busuanziToken)).first();
                if (token != null && token.getLong("expirationTime") > now) {
                    final String newBusuanziToken = UUIDs.uuid();
                    final long newExpirationTime = now + SECURITY_MAX_AGE;
                    tokens.updateOne(s("{un: '%s', sn: '%s'}", userName, serialNumber),
                            s("{$set: { tn: '%s', expirationTime: %s}, $currentDate: {LastUpdateTime: true}}", newBusuanziToken, newExpirationTime));

                    //更新过期时间和token
                    response.cookie(SECURITY_USER_NAME, userName, SECURITY_MAX_AGE);
                    response.cookie(SECURITY_SERIAL_NUMBER, serialNumber, SECURITY_MAX_AGE);
                    response.cookie(SECURITY_TOKEN, newBusuanziToken, SECURITY_MAX_AGE);

                    response.redirect("/manage/sites");
                }
            });
        });


        /* 注册 */
        post("/signup", (request, response) -> {
            // 用户名必须是邮箱，以便于日后增加验证（只有在必要的时候才会验证，如发现可疑行为）
            String username = request.queryParams("username");
            String password = request.queryParams("password");
            String repeatPassword = request.queryParams("repeatPassword");

            if (isNullOrEmpty(username) || isNullOrEmpty(password)) halt(ERROR_SIGNUP, "用户名或密码不能为空！");
            if (!password.equals(repeatPassword)) halt(ERROR_SIGNUP, "密码不一致！");
            if (!Asserts.isEmail(username)) halt(ERROR_SIGNUP, "用户名必须是邮箱！");
            if (password.length() < 8) halt(ERROR_SIGNUP, "密码不足8位！");

            MongoClient.wash("busuanzi/user", users -> {
                Document user = users.find(s("{username: '%s'}", username)).first();
                if (user != null) halt(ERROR_SIGNUP, "该用户名已被注册！");
                String encryptedPassword = encryptor.encryptPassword(password);
                users.insertOne(newDocument(
                        "username", username,
                        "password", encryptedPassword,
                        "signupTime", new Date()));
            });
            response.redirect("/signin.html");
            return Sugars.OK;
        });

        get("/signup_checkusername", (request, response) -> {

            String username = request.queryParams("username");

            return MongoClient.bath("busuanzi/user", users -> {
                Document user = users.find(newDocument("username", username)).first();
                return newHashMap("getdata", String.valueOf(user == null));
            });

        }, Sugars.SimpleJsonTransformer);

        // 验证码
        get("", (request, response) -> {

            return null;
        });

        // 校验密码黑名单
        get("/signup_checkpassword", (request, response) -> {
            Set<String> blacks = Containers.newHashSet("qwertyui", "12345678", "password", "88888888");
            String password = request.queryParams("password");
            return newHashMap("getdata", blacks.contains(password));
        }, Sugars.SimpleJsonTransformer);


        // 登入
        post("/signin", (request, response) -> {

            String username = request.queryParams("username");
            String password = request.queryParams("password");

            if (isNullOrEmpty(username) || isNullOrEmpty(password)) halt(ERROR_SIGNIN, "用户名或密码不能为空！");

            MongoClient.collection("busuanzi/user").wash(users -> {
                Document user = users.find(newDocument("username", username)).first();
                if (user == null) halt(ERROR_SIGNIN, "用户不存在或用户名与密码不匹配!");

                String encryptedPassword = user.getString("password");
                if (!encryptor.checkPassword(password, encryptedPassword)) halt(ERROR_SIGNIN, "用户不存在或用户名与密码不匹配!");
            });

            final String busuanziToken = UUIDs.uuid();
            final String serialNumber = UUIDs.randomLong();
            final long expirationTime = System.currentTimeMillis() + SECURITY_MAX_AGE;

            response.cookie(SECURITY_USER_NAME, username, SECURITY_MAX_AGE);
            response.cookie(SECURITY_SERIAL_NUMBER, serialNumber, SECURITY_MAX_AGE);
            response.cookie(SECURITY_TOKEN, busuanziToken, SECURITY_MAX_AGE);

            // 单例：只允许一个登录实例
            MongoClient.wash("busuanzi/token", tokens -> {
                tokens.findOneAndUpdate(
                        s("{un: '%s'}", username),
                        s("{$set: {sn: '%s', tn: '%s', expirationTime: %s}, $currentDate: {LastUpdateTime: true}}",
                                serialNumber, busuanziToken, expirationTime),
                        new FindOneAndUpdateOptions()
                                .upsert(true)
                                .maxTime(800, TimeUnit.MICROSECONDS)
                                .returnDocument(ReturnDocument.AFTER));
            });
            // response.redirect("/main.html");
            return Sugars.OK;
        });

        // 登出
        get("/signout", (request, response) -> {
            response.removeCookie(SECURITY_SERIAL_NUMBER);
            response.redirect("/signin.html");
            return Sugars.OK;

        });
    }

}
